package com.liziba.trend.service;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.liziba.trend.api.AnnualProfit;
import com.liziba.trend.api.IndexData;
import com.liziba.trend.api.Profit;
import com.liziba.trend.api.Trade;
import com.liziba.trend.feign.IndexDataClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;

/**
 * @program: trend
 * @description: 回测服务类
 * @author: liziba
 * @date: 2021-02-03 12:12
 */
@Service
public class BackTestService {

    @Autowired
    private IndexDataClient indexDataClient;

    /**
     * 获取指定基金代码的基金数据
     * @param code
     * @return
     */
    public List<IndexData> getIndexData(String code) {
        List<IndexData> indexCode = indexDataClient.getIndexCode(code);
        return indexCode;
    }

    /**
     * 返回前端数据
     * @param ma                均值范围
     * @param sellRate          买入值
     * @param buyRate           卖出值
     * @param serviceCharge     服务费
     * @param indexDatas        基金数据集合
     * @return
     */
    public Map<String, Object> simulate(int ma, float sellRate, float buyRate, float serviceCharge, List<IndexData> indexDatas) {
        List<Profit> profitList = new ArrayList<>();
        List<Trade> trades = new ArrayList<>();
        // 初始本金
        float initCash = 1000;
        float cash = initCash;
        // 份额
        float share = 0;
        // 初始本金 + 持有基金净值
        float value = 0;
        // 获取起始值，为第一天的收盘值
        float init = 0;

        // 盈利交易次数
        int winCount = 0;
        // 总共盈利率
        float totalWinRate = 0;
        // 平均盈利比率
        float avgWinRate = 0;
        // 亏损交易次数
        int lossCount = 0;
        // 总共亏损率
        float totalLossRate = 0;
        // 平均亏损比率
        float avgLossRate = 0;


        if(!indexDatas.isEmpty()) {
            init = indexDatas.get(0).getClosePoint();
        }

        for (int i = 0; i < indexDatas.size(); i++) {
            IndexData indexData = indexDatas.get(i);
            // 当前收盘点
            float closePoint = indexData.getClosePoint();
            // 求ma均值
            float avg = getMA(i, ma, indexDatas);
            // 最大收盘值
            float max = getMax(i, ma, indexDatas);
            // 收盘点 / 均线日期范围内的平均值
            float increaseRate = closePoint/ avg;
            // 收盘点 / 均线日期内的最大值
            float deceaseRate = closePoint/ max;

            if(avg != 0) {
                // 超过均线 && 份额 == 0 没有购买
                if((increaseRate > buyRate) && (0 == share)) {
                    // 全部买入
                    share = cash / closePoint;
                    cash = 0;

                    Trade trade = new Trade();
                    trade.setBuyClosePoint(closePoint);
                    trade.setBuyDate(indexData.getDate());
                    trade.setSellDate("n/a");
                    trade.setSellClosePoint(0);
                    trades.add(trade);
                }
                // 低于卖出值 && 份额 > 0 没有卖出
                else if ((deceaseRate < sellRate) && (0 != share)) {
                    // 全部卖出
                    cash = share * closePoint * (1 - serviceCharge);
                    share = 0;
                    // 取出上一次买入的Trade赋值
                    Trade trade = trades.get(trades.size() - 1);
                    trade.setSellClosePoint(closePoint);
                    trade.setSellDate(indexData.getDate());
                    trade.setRate(cash / initCash);
                    // 盈利
                    if(trade.getSellClosePoint() > trade.getBuyClosePoint()) {
                        totalWinRate += (trade.getSellClosePoint()-trade.getBuyClosePoint()) / trade.getBuyClosePoint();
                        winCount ++;
                    }
                    // 亏损
                    if(trade.getSellClosePoint() < trade.getBuyClosePoint()) {
                        totalLossRate += (trade.getSellClosePoint()-trade.getBuyClosePoint()) / trade.getBuyClosePoint();
                        lossCount ++;
                    }
                }
                else {
                    // do nothing
                }
            }
            // 计算本金 + 当前持有基金净值 = 总值
            if(share != 0) {
                value = share * closePoint;
            } else {
                value = cash;
            }
            // 计算收益率 = 总值 / 初始金额
            float rate = value / initCash;
            // 封装利率对象集合
            Profit profit = new Profit(indexData.getDate(), (rate * init));
            profitList.add(profit);
        }
        Map<String, Object> profitMap = new HashMap<>();
        // 趋势投资
        profitMap.put("profits", profitList);
        // 交易明细
        profitMap.put("trades", trades);
        // 盈、亏比率&次数
        avgWinRate = totalWinRate / winCount;
        avgLossRate = totalLossRate / lossCount;
        profitMap.put("winCount", winCount);
        profitMap.put("lossCount", lossCount);
        profitMap.put("avgWinRate", avgWinRate);
        profitMap.put("avgLossRate", avgLossRate);
        // 计算每一年指数投资和趋势投资的收益率
        profitMap.put("annualProfits", calculateAnnualProfit(indexDatas, profitList));
        return profitMap;
    }

    /**
     * 获取ma日期内的最大收盘值
     * @param i                 当前基金数据在查询集合indexDatas中的下标
     * @param ma                均线范围
     * @param indexDatas        查询基金数据集合
     * @return                  最大收盘值
     */
    private static float getMax(int i, int ma, List<IndexData> indexDatas) {
        // 开始时间
        int start = i - 1 - ma;
        if(start < 0) {
            start = 0;
        }
        // 结束时间
        int now = i - 1;
        // 获取ma日期范围内的最大值
        float max = 0;
        for(int j = start; j < now; j++) {
            IndexData indexData = indexDatas.get(j);
            if(indexData.getClosePoint() > max) {
                max = indexData.getClosePoint();
            }
        }
        return max;
    }

    /**
     * 求ma日期内均值
     * @param i                 当前基金数据在查询集合indexDatas中的下标
     * @param ma                均线范围
     * @param indexDatas        查询基金数据集合
     * @return                  均值
     */
    private static float getMA(int i, int ma, List<IndexData> indexDatas) {
        // 开始时间
        int start = i - 1 - ma;
        // 当前时间不足以计算该均值
        if(start < 0) {
            return 0;
        }
        // 结束时间
        int now = i - 1;
        // start~now 时间范围内的收盘点总和
        float sum = 0;
        //  start~now 时间范围内的均值
        float avg = 0;
        for(int j = start; j < now; j++) {
            IndexData indexData = indexDatas.get(j);
            sum += indexData.getClosePoint();
        }
        // 计算并返回均值
        avg = (sum / ma);
        return avg;
    }


    /**
     * 计算投资年份
     * @param indexDatas
     * @return
     */
    public static float getYears(List<IndexData> indexDatas) {
        String startDateStr = indexDatas.get(0).getDate();
        String endDateStr = indexDatas.get(indexDatas.size() - 1).getDate();
        // 开始日期
        Date startDate = DateUtil.parse(startDateStr);
        // 结束日期
        Date endDate = DateUtil.parse(endDateStr);
        // 天数
        long days = DateUtil.between(startDate, endDate, DateUnit.DAY);
        // 年
        float year = days / 365f;
        return year;
    }

    /**
     * 根据string日期类型获取年份
     * @param date      2019-10-01
     * @return          2019
     */
    private int getYear(String date) {
        return Convert.toInt(StrUtil.subBefore(date, "-", false));
    }

    /**
     * 获取指数投资的年收益率
     * @param year              当前年
     * @param indexDatas        基金数据
     * @return
     */
    private float getIndexIncome(int year, List<IndexData> indexDatas) {
        // 当前年初始基金净值
        IndexData firstIndexData = null;
        // 当前年结束基金净值
        IndexData lastIndexData = null;

        for (IndexData indexData : indexDatas) {
            // 获取日期年份 & 判断是否与传入年份相等
            int currentYear = getYear(indexData.getDate());
            if (year == currentYear) {
                if (null == firstIndexData){
                    firstIndexData = indexData;
                }
                lastIndexData = indexData;
            }
        }
        return (lastIndexData.getClosePoint() - firstIndexData.getClosePoint()) / firstIndexData.getClosePoint();
    }


    /**
     * 趋势投资年收益率
     * @param year              当前年
     * @param profits           趋势投资利润数据
     * @return
     */
    private float getTrendIncome(int year, List<Profit> profits) {
        // 当前年初始基金净值
        Profit firstProfit = null;
        // 当前年结束基金净值
        Profit lastProfit = null;

        for (Profit profit : profits) {
            // 获取日期年份 & 判断是否与传入年份相等
            int currentYear = getYear(profit.getDate());
            if (year == currentYear) {
                if (null == firstProfit){
                    firstProfit = profit;
                }
                lastProfit = profit;
            }
        }
        return (lastProfit.getValue() - firstProfit.getValue()) / firstProfit.getValue();
    }

    /**
     * 计算完整的时间范围内每一年的指数投资收益和趋势投资收益
     * @param indexDatas            指数投资基金
     * @param profits               趋势投资基金
     * @return
     */
    private List<AnnualProfit> calculateAnnualProfit(List<IndexData> indexDatas, List<Profit> profits) {
        List<AnnualProfit> annualProfits = new ArrayList<>();

        // 开始年份
        int startYear = getYear(indexDatas.get(0).getDate());
        // 结束年份
        int endYear = getYear(indexDatas.get(indexDatas.size() - 1).getDate());
        for(int year = startYear; year < endYear; year++) {
            AnnualProfit annualProfit = new AnnualProfit();
            float indexIncome = getIndexIncome(year, indexDatas);
            float trendIncome = getTrendIncome(year, profits);
            // 指数投资收益
            annualProfit.setIndexIncome(indexIncome);
            // 趋势投资收益
            annualProfit.setTrendIncome(trendIncome);
            // 年份
            annualProfit.setYear(year);
            annualProfits.add(annualProfit);
        }
        return annualProfits;
    }

}
